/*
	The Raja Toolkit.
	Copyright (C) 2011 Raja-Toolkit Foundation

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 3.0 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#ifndef RAJA_RENDER_GPU_TYPES_HPP
#define RAJA_RENDER_GPU_TYPES_HPP

#include "../types.hpp"
#include "../math/math.hpp"
#include "sampler2.hpp"

namespace raja
{
namespace render
{
struct program;
struct const_params;

///////////////////////////////////////////////////////////////////////////////
/// gpu data type
///////////////////////////////////////////////////////////////////////////////
enum GPU_DATA_TYPE
{
	GDT_ERROR		= -2,		///< error
	GDT_NONE		= -1,		///< no type

	GDT_REAL32		= 0,		///< a single float
	GDT_VEC2		= 1,		///< a 2d float vector
	GDT_VEC3		= 2,		///< a 3d float vector
	GDT_VEC4		= 3,		///< a 4d float vector

	GDT_SINT32		= 4,		///< a single int
	GDT_IVEC2		= 5,		///< a 2d int vector
	GDT_IVEC3		= 6,		///< a 3d int vector
	GDT_IVEC4		= 7,		///< a 4d int vector

	GDT_BOOL		= 8,		///< a single bool
	GDT_BVEC2		= 9,		///< a 2d bool vector
	GDT_BVEC3		= 10,		///< a 3d bool vector
	GDT_BVEC4		= 11,		///< a 4d bool vector

	GDT_MAT2		= 12,		///< a 2d float matrix
	GDT_MAT3		= 13,		///< a 3d float matrix
	GDT_MAT4		= 14,		///< a 4d float matrix

	GDT_SAMPLER2	= 15,		///< a 2d texture
	GDT_VERT_ARRAY	= 16,		///< a vertex array
	GDT_VERT_BUFFER	= 17,		///< a vertex buffer
	GDT_PRIM_ARRAY	= 18,		///< a primitive array
	GDT_PRIM_BUFFER	= 19,		///< a primitive buffer

	GDT_COUNT		= 20,		///< data type count (should always be in the end)
};	// enum DATA_TYPE

template<typename T>
const T& val(const const_params &b, const std::string &name);

template<typename T>
T& val(const_params &b, const std::string &name);

///
/// get_data_type method template forward declaration
/// @param t the variable to find the corresponding type for
/// @return the corresponding data type
///
template<typename T>
static inline GPU_DATA_TYPE			get_data_type();

struct const_params
{
	///
	/// add a constant field to the constant buffer
	/// @param name constant name
	/// @param val constant value
	///
	template<typename T>
	void			add_constant(const std::string &name, uint32 id, const T& val)
	{
		assert(__table.find(name) == __table.end());
		constant_info	c(name, __size, sizeof(val), 1, id, get_data_type<T>());
		uint8*	temp	= new uint8[__size + sizeof(val)];
		if( __data )
		{
			memcpy(temp, __data, __size + sizeof(val));
			delete[] __data;
		}
		__data	= temp;

		T*	v	= reinterpret_cast<T*>(&(__data[__size]));
		*v		= val;

		__size	+= sizeof(val);

		__table[name]	= c;
		__table.rehash(__table.size());
	}

	inline const_params(uint8* data, uint32 size) : __data(data), __size(size)//, __filled_count(0)
	{
	}

	template<typename T>
	friend const T& val(const const_params &b, const std::string &name);

	template<typename T>
	friend T& val(const_params &b, const std::string &name);

	///
	/// constant information
	///
	struct constant_info
	{
		std::string		get_name() const { return __name; }
		GPU_DATA_TYPE	get_type() const { return __type; }

		inline constant_info() : __offset(0), __element_size(0), __element_count(0), __type(GDT_NONE)
		{
		}

	private:
		std::string		__name;				///< constant name
		uint32			__offset;			///< offset inside the data buffer
		uint32			__element_size;		///< element size (this is implicit and should be removed later)
		uint32			__element_count;	///< element count
		uint32			__id;				///< id in the program
		GPU_DATA_TYPE	__type;				///< type

		inline constant_info(const std::string &name, uint32 offset, uint32 element_size, uint32 element_count, uint32 id, GPU_DATA_TYPE type) :
			__name(name), __offset(offset), __element_size(element_size), __element_count(element_count), __id(id), __type(type)
		{
		}

		friend struct	const_params;
		friend struct	program;

		template<typename T>
		friend const T& val(const const_params &b, const std::string &name);

		template<typename T>
		friend T& val(const_params &b, const std::string &name);
	};

	typedef boost::unordered_map<std::string, constant_info>::const_iterator	const_iterator;

	inline const_iterator begin() const
	{
		return __table.begin();
	}

	inline const_iterator end() const
	{
		return __table.end();
	}

	///
	/// default constructor should be hidden
	///
	inline const_params() : __data(0), __size(0) { std::cout << "constructor" << std::endl; }

	///
	/// copy constructor
	///
	inline const_params(const const_params &in) : __table(in.__table), __size(in.__size)
	{
		__data	= new uint8[__size];
		memcpy(__data, in.__data, __size);
		std::cout << "const_params: copy constructor: " << __size << std::endl;
	}

	///
	/// equal operator
	///
	inline const_params& operator = (const const_params &in)
	{
		__table	= in.__table;
		__size	= in.__size;
		__data	= new uint8[__size];
		memcpy(__data, in.__data, __size);
		std::cout << "const_params: operator =: " << __size << std::endl;
		return *this;
	}

	///
	/// destructor
	///
	~const_params()
	{
		std::cout << "~const_params" << std::endl;
	}

private:

	boost::unordered_map<std::string, constant_info>	__table;

	uint8*			__data;

	uint32			__size;

	friend struct	program;
};	// struct const_buffer



template<> inline GPU_DATA_TYPE		get_data_type<real32>() { return GDT_REAL32; }
template<> inline GPU_DATA_TYPE		get_data_type<math::vec2>() { return GDT_VEC2; }
template<> inline GPU_DATA_TYPE		get_data_type<math::vec3>() { return GDT_VEC3; }
template<> inline GPU_DATA_TYPE		get_data_type<math::vec4>() { return GDT_VEC4; }

template<> inline GPU_DATA_TYPE		get_data_type<sint32>() { return GDT_SINT32; }
template<> inline GPU_DATA_TYPE		get_data_type<math::ivec2>() { return GDT_IVEC2; }
template<> inline GPU_DATA_TYPE		get_data_type<math::ivec3>() { return GDT_IVEC3; }
template<> inline GPU_DATA_TYPE		get_data_type<math::ivec4>() { return GDT_IVEC4; }

template<> inline GPU_DATA_TYPE		get_data_type<bool>() { return GDT_REAL32; }
template<> inline GPU_DATA_TYPE		get_data_type<math::bvec2>() { return GDT_BVEC2; }
template<> inline GPU_DATA_TYPE		get_data_type<math::bvec3>() { return GDT_BVEC3; }
template<> inline GPU_DATA_TYPE		get_data_type<math::bvec4>() { return GDT_BVEC4; }

template<> inline GPU_DATA_TYPE		get_data_type<math::mat2>() { return GDT_MAT2; }
template<> inline GPU_DATA_TYPE		get_data_type<math::mat3>() { return GDT_MAT3; }
template<> inline GPU_DATA_TYPE		get_data_type<math::mat4>() { return GDT_MAT4; }

template<> inline GPU_DATA_TYPE		get_data_type<render::sampler2_ptr>() { return GDT_SAMPLER2; }

///
/// constant value getter
/// @param b the const_params buffer
/// @param name constant name
/// @return constant value
///
template<typename T>
const T& val(const const_params &b, const std::string &name)
{
	const_params::const_iterator it = b.__table.find(name);
	assert( it != b.__table.end());
	//const const_params::constant_info c = b.__table[name];
	const_params::constant_info c = (*it).second;
	assert(get_data_type<T>() == c.get_type() );
	assert(c.__element_count == 1); // needed ?
	T*	v	= reinterpret_cast<T*>(&(b.__data[c.__offset]));
	return *v;
}

///
/// constant value getter and setter
/// @param b the const_params buffer
/// @param name constant name
/// @return constant value/reference
///
template<typename T>
T& val(const_params &b, const std::string &name)
{
	assert(b.__table.find(name) != b.__table.end());
	const_params::constant_info& c = b.__table[name];
	assert(get_data_type<T>() == c.get_type() );
	assert(c.__element_count == 1); // needed ?
	T*	v	= reinterpret_cast<T*>(&(b.__data[c.__offset]));
	return *v;
}

///
/// get the name of the uniform type
/// @param type uniform type enum
/// @return name of glsl the uniform type
///
std::string get_name_of_type(GPU_DATA_TYPE type);

///
/// get the name of the uniform type
/// @param type uniform type enum
/// @return name of glsl the uniform type
///
size_t get_size_of_type(GPU_DATA_TYPE type);

///////////////////////////////////////////////////////////////////////////////
/// vertex declaration
/// this is returned by the program and can be composed by data loaders.
/// @todo: make a system that does the conversion
///////////////////////////////////////////////////////////////////////////////
struct vertex_declaration
{
	///
	/// vertex attribute information
	///
	struct attrib
	{
		enum
		{
			MAX_OFFSET	= 0xFFFF,
		};
		attrib() : __type(GDT_NONE)			{}
		attrib(const std::string &name, GPU_DATA_TYPE type) : __name(name), __type(type), __offset(MAX_OFFSET)	{}

		std::string			get_name() const { return __name; }
		GPU_DATA_TYPE		get_type() const { return __type; }

		///
		/// get the attribute offset.
		///
		/// @note When the vertex declaration is of DT_BUFFER type, this means
		/// the offset of the attribute. When the declaration is of
		/// DT_PROGRAM type, this means the location of the attrib inside
		/// the program.
		///
		/// @return the offset
		///
		uint32				get_offset() const { return __offset; }

	private:
		std::string			__name;
		GPU_DATA_TYPE		__type;
		uint32				__offset;

		friend struct		vertex_declaration;
	};

	typedef boost::unordered_map<std::string, attrib>	attrib_map;
	typedef attrib_map::const_iterator	const_iterator;

	///
	/// construct the vertex declaration from a list of attributes
	///
	/// @param attribs attribute list
	/// @param type offset in memory or location in the program
	///
	vertex_declaration(const std::vector<attrib> &attribs) : __vertex_size(0)
	{
		uint32		offset	= 0;
		for( size_t i = 0; i < attribs.size(); i++ )
		{
			__attrib_map[attribs[i].__name]	= attribs[i];
			if( attribs[i].__offset == attrib::MAX_OFFSET )
				__attrib_map[attribs[i].__name].__offset	= offset;
			offset	+= get_size_of_type(attribs[i].__type);
			std::cout << "vertex " << attribs[i].__name << " at offset " << __attrib_map[attribs[i].__name].__offset << std::endl;
		}
		__vertex_size		= offset;
	}

	/// destructor
	~vertex_declaration()
	{
		std::cout << "~vertex_declaration" << std::endl;
	}

	uint32					get_attrib_count() const	{ return __attrib_map.size(); }
	uint32					get_vertex_size() const		{ return __vertex_size; }
	const_iterator			begin() const				{ return __attrib_map.begin(); }
	const_iterator			end() const					{ return __attrib_map.end(); }

	const attrib&			get_attrib(const std::string &name) const
	{
		const_iterator	it	= __attrib_map.find(name);
		assert( it != __attrib_map.end());
		return (*it).second;
	}

private:
	uint32					__vertex_size;
	attrib_map				__attrib_map;
};

}	// namespace render
}	// namespace raja
#endif // RAJA_RENDER_GPU_TYPES_HPP
